package bootstrap

import (
	"embed"
	"errors"
	"fmt"
	"iris-wms/apps/admin/middleware/auth"
	"iris-wms/common/model"
	"iris-wms/common/pkg"
	"iris-wms/common/pkg/db"
	"iris-wms/common/pkg/ibbolt"
	"iris-wms/common/tools"
	"iris-wms/common/tools/consts"
	"iris-wms/common/tools/result"
	"strings"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/go-sql-driver/mysql"
	"github.com/iris-contrib/middleware/cors"
	"github.com/kataras/golog"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/middleware/rate"
	"github.com/kataras/iris/v12/middleware/recover"
	"github.com/kataras/iris/v12/websocket"
	"github.com/kataras/jwt"
	"gorm.io/gorm"
)

type Configurator func(*Bootstrapper)

type Bootstrapper struct {
	*iris.Application
	AppName      string
	AppOwner     string
	AppSpawnDate time.Time
	confFS       embed.FS
	viewFS       embed.FS
}

func New(confFS, viewFS embed.FS) *Bootstrapper {
	tools.InitGlobalConfig(confFS)
	b := &Bootstrapper{
		AppName:      tools.GConfig.Boot.Servername,
		AppOwner:     tools.GConfig.Boot.AppOwner,
		AppSpawnDate: time.Now(),
		Application:  iris.New(),
		confFS:       confFS,
		viewFS:       viewFS,
	}
	return b
}

func (b *Bootstrapper) Configure(cs ...Configurator) {
	for _, c := range cs {
		c(b)
	}
	b.buildPolicy()
}

func (b *Bootstrapper) buildPolicy() {
	if !tools.GConfig.App.Gorm.InitPolicy {
		return
	}
	var list = make([]*model.AdminPolicy, 0)
	for i, v := range b.GetRoutes() {
		splits := strings.Split(v.Title, "~")
		if len(splits) >= 2 {
			if v.Method == "GET" || v.Method == "POST" || v.Method == "DELETE" || v.Method == "PUT" {
				list = append(list, &model.AdminPolicy{
					Model:     gorm.Model{ID: uint(i + 1)},
					GroupName: splits[0],
					Name:      splits[1],
					Memo: func() string {
						if len(splits) > 2 {
							return splits[2]
						}
						return ""
					}(),
					V1: tools.GConfig.App.Casbin.Tenant,
					V2: v.Path,
					V3: v.Method,
					V4: ".*",
					V5: "",
				})
			}
		}
	}
	db.GDB1.Transaction(func(tx *gorm.DB) error {
		for _, v := range list {
			err := tx.Where(model.AdminPolicy{Model: gorm.Model{ID: v.ID}}).FirstOrCreate(&v).Error
			if err != nil {
				return err
			}
		}
		return nil
	})
}

func (b *Bootstrapper) Bootstrap() *Bootstrapper {
	b.init()
	b.Validator = validator.New()
	b.Use(iris.Compression)
	b.Use(recover.New())
	//
	b.SetupViews()
	b.SetupErrorHandlers()
	b.SetupOther()
	b.SetupCors()
	b.SetupMonitor()
	b.Setuplimiter()
	return b
}

func (b *Bootstrapper) init() {
	b.initLogger()
	pkg.InitGid()
	ibbolt.InitBblot()
	db.InitGDb1()
	//
	auth.InitAuth(b.confFS)
}

func (b *Bootstrapper) SetupWebsockets(endpoint string, handler websocket.ConnHandler) {
	ws := websocket.New(websocket.DefaultGorillaUpgrader, handler)
	b.Get(endpoint, websocket.Handler(ws))
}

func (b *Bootstrapper) SetupViews() {
	b.Favicon("./resources/favicon.ico")
	b.HandleDir("/", b.viewFS)
}

func (b *Bootstrapper) Listen() {
	if err := b.Run(
		iris.Addr(fmt.Sprintf("0.0.0.0:%d", tools.GConfig.Boot.Port)),
		iris.WithConfiguration(tools.GConfig.Boot.Configuration),
	); err != nil {
		golog.Fatalf("%s start err:%v", tools.GConfig.Boot.Servername, err)
	}
}

func (b *Bootstrapper) Setuplimiter() {
	limiter := rate.Limit(1, 10, rate.PurgeEvery(time.Minute, time.Minute*5))
	b.Use(limiter)
}

func (b *Bootstrapper) SetupCors() {
	crs := cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"*"},
		AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		//Debug:            true,
	})
	b.UseRouter(crs)
}

func (b *Bootstrapper) SetupMonitor() {
	// statsvizPath := "/statsviz/"
	// serveRoot := statsviz.IndexAtRoot(statsvizPath)
	// serveWS := statsviz.NewWsHandler(time.Second * 6)
	// b.UseRouter(func(ctx iris.Context) {
	// 	if strings.HasPrefix(ctx.Path(), statsvizPath+"ws") {
	// 		serveWS(ctx.ResponseWriter(), ctx.Request())
	// 	} else if strings.HasPrefix(ctx.Path(), statsvizPath) {
	// 		serveRoot(ctx.ResponseWriter(), ctx.Request())
	// 	} else {
	// 		ctx.Next()
	// 	}
	// })
}

func (b *Bootstrapper) SetupOther() {
	b.SetExecutionRules(iris.ExecutionRules{
		Done: iris.ExecutionOptions{Force: true},
	})
	b.UseGlobal(func(ctx iris.Context) {
		ctx.Header("App-Name", b.AppName)
		ctx.Header("App-Owner", b.AppOwner)
		ctx.Header("App-Since", time.Since(b.AppSpawnDate).String())
		ctx.Header("Server", "Iris: https://iris-go.com")
		// ctx.Values().Set("st", time.Now().UnixMicro())
		ctx.Next()
	})
}

func (b *Bootstrapper) SetupDoneGlobal() {
	// for _, v := range sout.GPolicyLogList {
	// 	if v.V2 == ctx.Path() && v.V3 == ctx.Method() {
	// 		st := ctx.Values().GetInt64Default("st", 0)
	// 		diff := time.Since(time.UnixMicro(st)).Milliseconds() // 毫秒 1/1000s
	// 		params, _ := json.Marshal(ctx.URLParams())
	// 		body, _ := ctx.GetBody()
	// 		var groupName, name string
	// 		routeStr := strings.Split(ctx.GetCurrentRoute().String(), " ")
	// 		if len(routeStr) > 0 {
	// 			title := routeStr[0]
	// 			arr := strings.Split(title, "~")
	// 			if len(arr) >= 2 {
	// 				groupName = arr[0]
	// 				name = arr[1]
	// 			}
	// 		}
	// userAgent := ctx.Request().UserAgent()
	// var userAgent2 string = "未知"
	// if strings.Contains(userAgent, "Chrome") {
	// 	userAgent2 = "Chrome"
	// }
	// if strings.Contains(userAgent, "Firefox") {
	// 	userAgent2 = "Firefox"
	// }
	// GLogSink.Append(&modeladmin.AdminLog{
	// 	Model: model.Model{
	// 		CreatorId: ctx.Values().GetInt64Default(tools.Uid, 0),
	// 	},
	// 	IpAddr:         ctx.RemoteAddr(),
	// 	City:           ctx.RemoteAddr(),
	// 	Os:             strings.Replace(ctx.Request().Header.Get("Sec-Ch-Ua-Platform"), "\"", "", -1),
	// 	UserAgent:      userAgent2,
	// 	Path:           ctx.Path(),
	// 	Method:         ctx.Method(),
	// 	Params:         string(params),
	// 	Data:           string(body),
	// 	GroupName:      groupName,
	// 	Name:           name,
	// 	Duration:       int(diff),
	// 	RespStatusCode: ctx.ResponseWriter().StatusCode(),
	// })
	// 	}
	// }
}

func (b *Bootstrapper) initLogger() {
	logger := tools.NewLogger()
	// golog.Install(logger)
	golog.SetLevel(tools.GConfig.Boot.LogLevel)
	b.Logger().SetOutput(logger.Out)
	golog.Infof("%s is starting...", tools.GConfig.Boot.Servername)
}

func (b *Bootstrapper) SetupErrorHandlers() {
	b.OnAnyErrorCode(func(ctx iris.Context) {
		ctx.Application().Logger().Errorf(
			"Path=[%s]，状态码=[%d]，用户ID=[%v]。\n异常信息：%v",
			ctx.Path(),
			ctx.GetStatusCode(),
			ctx.Values().GetInt64Default(consts.UID, 0),
			ctx.GetErr())

		status := ctx.GetStatusCode()
		err := ctx.GetErr()
		// wms
		if errors.Is(err, consts.Wms_Bill_Not_Goods) {
			result.Other(ctx, iris.StatusBadRequest, result.Wms_Code_Bill_Not_Goods)
			return
		}
		if errors.Is(err, consts.Wms_Bill_Ountbound_Not_Qty) {
			result.Other(ctx, iris.StatusBadRequest, result.Wms_Bill_Ountbound_Not_Qty)
			return
		}
		if errors.Is(err, consts.Wms_Bill_Pandian_Qty_Noteq) {
			result.Other(ctx, iris.StatusBadRequest, result.Wms_Bill_Pandian_Qty_Noteq)
			return
		}
		if errors.Is(err, consts.Wms_Bill_Pandian_Overed) {
			result.Other(ctx, iris.StatusBadRequest, result.Wms_Bill_Pandian_Overed)
			return
		}
		if errors.Is(err, consts.Wms_Bill_Pandian_Qty_Not_Changed) {
			result.Other(ctx, iris.StatusBadRequest, result.Wms_Bill_Pandian_Qty_Not_Changed)
			return
		}
		// jwt
		if errors.Is(err, jwt.ErrMissing) {
			result.Other(ctx, status, result.Code_Token_Empty)
			return
		}
		if errors.Is(err, jwt.ErrExpired) {
			result.Other(ctx, status, result.Code_Token_Expired)
			return
		}
		if errors.Is(err, jwt.ErrInvalidKey) {
			result.Other(ctx, status, result.Code_Token_Invalid)
			return
		}
		// sql error
		if sqlErr, ok := err.(*mysql.MySQLError); ok {
			if sqlErr.Number == 1062 {
				result.Other(ctx, iris.StatusBadRequest, result.Code_DataDuplicate)
				return
			}
		}

		code := func(code any) result.InnerCode {
			if code != nil {
				return code.(result.InnerCode)
			} else {
				return result.InnerCode(status)
			}
		}(ctx.Values().Get(consts.INNER_CODE_KEY))
		// RFC
		if status == iris.StatusBadRequest {
			result.BadRequest(ctx, code)
			return
		}
		if status == iris.StatusNotFound {
			result.Other(ctx, status, code)
			return
		}
		// 401=未认证
		if status == iris.StatusUnauthorized {
			result.Other(ctx, status, code)
			return
		}
		// 403=已通过认证,但没有对应权限
		if status == iris.StatusForbidden {
			result.Other(ctx, status, code)
			return
		}
		// 5xx=服务器错误
		if status >= iris.StatusInternalServerError {
			result.OtherData(ctx, status, code, nil)
			return
		}
	})
}
